package tr3dtris.game.models;

import tr3dtris.game.models.pieces.AbstractPiece;

/**
 * Class that represents a sort of grid of <Cell>s. A <CellArray> is used to
 * collects all the <Block>s of a piece or to collect the whole game scheme of
 * the falling blocks.
 * CellArray class is implemented as a rectangular matrix of <Cell>s.
 * @author Luciano Mammino <loige@hotmail.com>
 * @author Andrea Mangano <kapitan0@hotmail.it>
 * @version 0.1
 * @since 0.1
 */
public class CellArray implements Cloneable
{
    /**
     * Default width constant
     */
    public static final int DEFAULT_WIDTH = 10;

    /**
     * Default height constant
     */
    public static final int DEFAULT_HEIGHT = 20;

    /**
     * Bidimensional array of cell. Used to represent a sort of board (or grid)
     * in wich <Block>s are disposed.
     */
    protected Cell[][] cells;

    /**
     *
     */
    protected int width;
    protected int height;


    public CellArray()
    {
        this(CellArray.DEFAULT_WIDTH, CellArray.DEFAULT_HEIGHT);
    }


    public CellArray(int width, int height)
    {
        this.width = width;
        this.height = height;
        this.clear();
    }

    /**
     * @return the cells
     */
    public Cell[][] getCells()
    {
        return cells;
    }

    /**
     * @param cells the cells to set
     */
    public void setCells(Cell[][] cells)
    {
        this.cells = cells;
    }

    /**
     * @return the width
     */
    public int getWidth()
    {
        return width;
    }

    /**
     * @param width the width to set
     */
    public void setWidth(int width)
    {
        this.width = width;
    }

    /**
     * @return the height
     */
    public int getHeight()
    {
        return height;
    }

    /**
     * @param height the height to set
     */
    public void setHeight(int height)
    {
        this.height = height;
    }

    /**
     * Add all the block of a given <AbstractPiece> instance and fill the
     * cells with all the <AbstractPiece> <Block>s starting from the position
     * (0,0).
     * @param piece
     */
    public void storePiece(AbstractPiece piece)
    {
        this.storePiece(piece, 0, 0);
    }

    /**
     * Add all the block of a given <AbstractPiece> instance and fill the
     * cells with all the <AbstractPiece> <Block>s starting from the position
     * (x,y).
     * @param piece
     * @param x
     * @param y
     */
    public void storePiece(AbstractPiece piece, int x, int y)
    {
        for(int pY=0; pY < piece.getHeight(); pY++)
        {
            for(int pX=0; pX < piece.getWidth(); pX++)
            {
                if(piece.getCells().hasBlockAt(pX, pY))
                {
                    this.addBlockAt(x + pX, y + pY, piece.getCells().getBlockAt(pX, pY));
                }
            }
        }
    }


    /**
     * Removes all the blocks from all the #cells
     */
    public void clear()
    {
        this.setCells(new Cell[this.getWidth()][this.getHeight()]);
        for(int y=0; y < this.getHeight(); y++)
            for(int x=0; x < this.getWidth(); x++)
                this.clearCell(x,y);
    }

    /**
     * Removes the Block inside the cell with coordinates (x,y). The cell became
     * empty.
     * @param x
     * @param y
     */
    public void clearCell(int x, int y)
    {
        this.getCells()[x][y] = new Cell();
    }

    /**
     * Sets a given cell in the #cells grid at coordinates (x,y) replacing the
     * old cell at the same coordinates.
     * @param x
     * @param y
     * @param cell
     */
    public void setCellAt(int x, int y, Cell cell)
    {
        this.getCells()[x][y] = cell;
    }

    /**
     * Get a cell at given coordinates
     * @param x
     * @param y
     * @return
     */
    public Cell getCellAt(int x, int y)
    {
        return this.getCells()[x][y];
    }

    /**
     * Get a Block inside a cell at given coordinates.
     * If the cell is empty null is returned
     * @param x
     * @param y
     * @return
     */
    public Block getBlockAt(int x, int y)
    {
        return this.getCellAt(x, y).getBlock();
    }

    /**
     * Adds a block at given coordinates. If a block already exist (the cell is
     * not empty) that block is replaced.
     * @param x
     * @param y
     * @param block
     */
    public void addBlockAt(int x, int y, Block block)
    {
        this.getCellAt(x, y).setBlock(block);
    }

    /**
     * Verify if there's a block at given coordinates
     * @param x
     * @param y
     * @return
     */
    public boolean hasBlockAt(int x, int y)
    {
        //if coordinates are inside the grid checks wheter the given cell
        //has a block or not
        if(this.isInside(x, y))
            return (this.getBlockAt(x, y) != null);

        //if coordinates are outside the grid return false
        return false;
    }

    /**
     * Checks if given coordinates are inside the grid
     * @param _x X coordinate
     * @param _y Y coordinate
     * @return
     */
    public boolean isInside(int _x, int _y)
    {
        return this.isInside(_x, _y, true);
    }

    /**
     * Checks if given coordinates are inside the grid.
     * Can also specify to check wheter the cell is outside from the top.
     * @param _x X coordinate
     * @param _y Y coordinate
     * @param checkTop if this is set to true checks also if the cell is outside
     * from the top. Sometimes a cell outside from the top maybe considered
     * inside. So in this cases you can set this parameters to false.
     * @return
     */
    public boolean isInside(int _x, int _y, boolean checkTop)
    {
        if( _x >= this.width ||
            _x < 0    ||
            _y >= this.height
          )
            return false;

        if( checkTop && _y < 0)
            return false;

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object clone()
    {
        CellArray clone = new CellArray(this.getWidth(), this.getHeight());

        for(int y=0; y < clone.getHeight(); y++)
        {
            for(int x=0; x < clone.getWidth(); x++)
            {
                if(this.hasBlockAt(x, y))
                    clone.addBlockAt(x, y, (Block)this.getBlockAt(x, y));
            }
        }

        return clone;
    }

}
